﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.Tools.DataGenerator;
using Microsoft.Data.Schema.Tools.DataGenerator.Generators;
using Microsoft.Data.Schema.Tools.Settings;
using InHouse.SharedLibraries.DataContext;
using System.Data;

namespace InHouse.TFSExtensions.DBDataGenerators
{
    public class StaticDataGeneratorDesigner : DefaultGeneratorDesigner, IDisposable
    {
        private static Dictionary<string,OutputDescriptor[]> _OutputCache = new Dictionary<string,OutputDescriptor[]>();
        private static Dictionary<string, object> _LockObjects = new Dictionary<string, object>();
        private static int _ReferenceCount = 0;
        private string _ConnectionString;
        private string _tableName;

        
        public override InputDescriptor[] GetInputs()
        {
            return base.GetInputs();           
        }

        private string UniqueKey()
        {
            return this._ConnectionString + this._tableName;
        }

        private bool InOutputCache()
        {
            return _OutputCache.ContainsKey(UniqueKey());
        }

        public override void OnInputValueChanged(object sender, InputChangedEventArgs eventArgs)
        {
            base.OnInputValueChanged(sender, eventArgs);
            LockedOutputCacheChange(new Func<bool>(() =>
            {
                if (InOutputCache())
                {
                    _OutputCache.Remove(UniqueKey());
                    return true;
                }
                return false;
            }));

            if (eventArgs.Input.Key == "ConnectionString")
                _ConnectionString = eventArgs.Input.Value.ToString();

            if (eventArgs.Input.Key == "TableIdentifier")
                _tableName = eventArgs.Input.Value.ToString();

            LoadTableFormat();
            
        }

        

        public override void Initialize(DesignerInit initInfo)
        {
            base.Initialize(initInfo);
            _ReferenceCount++;
            
        }

        public override void SetDefaultInputValues(InputDescriptor[] inputs)
        {
            base.SetDefaultInputValues(inputs);
            foreach (InputDescriptor input in inputs)
            {
                if (input.Key == "TableIdentifier")
                    _tableName = input.Value.ToString();
            }
        }

        private bool LockedOutputCacheChange(Func<bool> perform)
        {
            if (_ConnectionString != null && this._tableName != null)
            {
                if (!_LockObjects.ContainsKey(UniqueKey()))
                {
                    lock (_LockObjects)
                    {
                        if (!_LockObjects.ContainsKey(UniqueKey()))
                            //Create a new lock object
                            _LockObjects[UniqueKey()] = new object();
                    }
                }

                lock (_LockObjects[UniqueKey()])
                {
                        return perform();
                }
            }
            return false;
        }

        private void LoadTableFormat()
        {
            LockedOutputCacheChange(new Func<bool>(() =>
                {
                    if (!InOutputCache())
                    {
                        List<OutputDescriptor> list = new List<OutputDescriptor>();
                        string sqlTemplate = "set fmtonly on\nselect * from {0}";
                        OutputDescriptor[] outputs = base.GetOutputs();
                        list.AddRange(outputs);
                        if ((this._ConnectionString != null))
                        {
                            OutputDescriptor descriptor;
                            DataContext dc = new DataContext(_ConnectionString);


                            DataTable result = dc.FillDataTable(string.Format(sqlTemplate, _tableName),
                                null);

                            foreach (DataColumn col in result.Columns)
                            {
                                descriptor = new OutputDescriptor(col.ColumnName, col.DataType)
                                {
                                    Name = col.ColumnName,
                                    Description = "No description available"
                                };
                                list.Add(descriptor);
                            }

                        }
                        _OutputCache[UniqueKey()] = list.ToArray();
                        return true;
                    }
                    else
                        return false;
                }));
        }

        public override OutputDescriptor[] GetOutputs()
        {
            LoadTableFormat();
            if (InOutputCache())
                return _OutputCache[UniqueKey()];
            else
                return base.GetOutputs();
        }

        
        public override void OnInputChangeValidation(object sender, InputChangingEventArgs eventArgs)
        {
            base.OnInputChangeValidation(sender, eventArgs);
            //One of the properties is not set and is not being set by the user.  No need to validate at this point.
            if ((this._ConnectionString == null && eventArgs.Input.Key != "ConnectionString") ||
                (this._tableName == null && eventArgs.Input.Key != "TableIdentifier"))
                return;

            //Local variables for validation
            string connectionString;
            string tableName;

            connectionString = (eventArgs.Input.Key == "ConnectionString" ? eventArgs.Value.ToString() : _ConnectionString);
            tableName = (eventArgs.Input.Key == "TableIdentifier" ? eventArgs.Value.ToString() : _tableName);

            DataContext dc = new DataContext(connectionString);
            string sqlTemplate = 
@"if exists (select top 1 1 from information_schema.tables where table_name = @tableName)
	select 1 as indatabase
else
	select 0 as indatabase";
            ParamaterCollection pc = new ParamaterCollection();
            pc.Add("@tableName", tableName);
            DataTable result = null;
            try
            {
                result = dc.FillDataTable(sqlTemplate, pc);
            }
            catch (Exception e)
            {
                throw new InputValidationException(
                    string.Format("There has been an error checking for the existence of table {0} using connection string \"{1}\"",
                    tableName, connectionString),Severity.Error, e);
            }

            if ((int)result.Rows[0]["indatabase"] == 0)
                throw new InputValidationException(string.Format("Table {0} does not exist.", tableName), Severity.Error);
        }



        public void Dispose()
        {
            _ReferenceCount--;
            if (_ReferenceCount <= 0)
            {
                _ReferenceCount = 0;
                _LockObjects.Clear();
                _OutputCache.Clear();
            }
        }
    }
}
